// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/timer/timer.h"

#include <stddef.h>

#include <memory>

#include "base/macros.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/test/test_simple_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"

using base::SingleThreadTaskRunner;
using base::TimeDelta;

namespace {

// The message loops on which each timer should be tested.
const base::MessageLoop::Type testing_message_loops[] = {
    base::MessageLoop::TYPE_DEFAULT,
    base::MessageLoop::TYPE_IO,
#if !defined(OS_IOS) // iOS does not allow direct running of the UI loop.
    base::MessageLoop::TYPE_UI,
#endif
};

const int kNumTestingMessageLoops = arraysize(testing_message_loops);

class OneShotTimerTester {
public:
    explicit OneShotTimerTester(bool* did_run, unsigned milliseconds = 10)
        : did_run_(did_run)
        , delay_ms_(milliseconds)
        , quit_message_loop_(true)
    {
    }

    void Start()
    {
        timer_.Start(FROM_HERE, TimeDelta::FromMilliseconds(delay_ms_), this,
            &OneShotTimerTester::Run);
    }

    void SetTaskRunner(scoped_refptr<SingleThreadTaskRunner> task_runner)
    {
        quit_message_loop_ = false;
        timer_.SetTaskRunner(task_runner);
    }

private:
    void Run()
    {
        *did_run_ = true;
        if (quit_message_loop_) {
            base::MessageLoop::current()->QuitWhenIdle();
        }
    }

    bool* did_run_;
    base::OneShotTimer timer_;
    const unsigned delay_ms_;
    bool quit_message_loop_;
};

class OneShotSelfDeletingTimerTester {
public:
    explicit OneShotSelfDeletingTimerTester(bool* did_run)
        : did_run_(did_run)
        , timer_(new base::OneShotTimer())
    {
    }

    void Start()
    {
        timer_->Start(FROM_HERE, TimeDelta::FromMilliseconds(10), this,
            &OneShotSelfDeletingTimerTester::Run);
    }

private:
    void Run()
    {
        *did_run_ = true;
        timer_.reset();
        base::MessageLoop::current()->QuitWhenIdle();
    }

    bool* did_run_;
    std::unique_ptr<base::OneShotTimer> timer_;
};

class RepeatingTimerTester {
public:
    explicit RepeatingTimerTester(bool* did_run, const TimeDelta& delay)
        : did_run_(did_run)
        , counter_(10)
        , delay_(delay)
    {
    }

    void Start()
    {
        timer_.Start(FROM_HERE, delay_, this, &RepeatingTimerTester::Run);
    }

private:
    void Run()
    {
        if (--counter_ == 0) {
            *did_run_ = true;
            timer_.Stop();
            base::MessageLoop::current()->QuitWhenIdle();
        }
    }

    bool* did_run_;
    int counter_;
    TimeDelta delay_;
    base::RepeatingTimer timer_;
};

void RunTest_OneShotTimer(base::MessageLoop::Type message_loop_type)
{
    base::MessageLoop loop(message_loop_type);

    bool did_run = false;
    OneShotTimerTester f(&did_run);
    f.Start();

    base::RunLoop().Run();

    EXPECT_TRUE(did_run);
}

void RunTest_OneShotTimer_Cancel(base::MessageLoop::Type message_loop_type)
{
    base::MessageLoop loop(message_loop_type);

    bool did_run_a = false;
    OneShotTimerTester* a = new OneShotTimerTester(&did_run_a);

    // This should run before the timer expires.
    base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, a);

    // Now start the timer.
    a->Start();

    bool did_run_b = false;
    OneShotTimerTester b(&did_run_b);
    b.Start();

    base::RunLoop().Run();

    EXPECT_FALSE(did_run_a);
    EXPECT_TRUE(did_run_b);
}

void RunTest_OneShotSelfDeletingTimer(
    base::MessageLoop::Type message_loop_type)
{
    base::MessageLoop loop(message_loop_type);

    bool did_run = false;
    OneShotSelfDeletingTimerTester f(&did_run);
    f.Start();

    base::RunLoop().Run();

    EXPECT_TRUE(did_run);
}

void RunTest_RepeatingTimer(base::MessageLoop::Type message_loop_type,
    const TimeDelta& delay)
{
    base::MessageLoop loop(message_loop_type);

    bool did_run = false;
    RepeatingTimerTester f(&did_run, delay);
    f.Start();

    base::RunLoop().Run();

    EXPECT_TRUE(did_run);
}

void RunTest_RepeatingTimer_Cancel(base::MessageLoop::Type message_loop_type,
    const TimeDelta& delay)
{
    base::MessageLoop loop(message_loop_type);

    bool did_run_a = false;
    RepeatingTimerTester* a = new RepeatingTimerTester(&did_run_a, delay);

    // This should run before the timer expires.
    base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, a);

    // Now start the timer.
    a->Start();

    bool did_run_b = false;
    RepeatingTimerTester b(&did_run_b, delay);
    b.Start();

    base::RunLoop().Run();

    EXPECT_FALSE(did_run_a);
    EXPECT_TRUE(did_run_b);
}

class DelayTimerTarget {
public:
    bool signaled() const { return signaled_; }

    void Signal()
    {
        ASSERT_FALSE(signaled_);
        signaled_ = true;
    }

private:
    bool signaled_ = false;
};

void RunTest_DelayTimer_NoCall(base::MessageLoop::Type message_loop_type)
{
    base::MessageLoop loop(message_loop_type);

    // If Delay is never called, the timer shouldn't go off.
    DelayTimerTarget target;
    base::DelayTimer timer(FROM_HERE, TimeDelta::FromMilliseconds(1), &target,
        &DelayTimerTarget::Signal);

    bool did_run = false;
    OneShotTimerTester tester(&did_run);
    tester.Start();
    base::RunLoop().Run();

    ASSERT_FALSE(target.signaled());
}

void RunTest_DelayTimer_OneCall(base::MessageLoop::Type message_loop_type)
{
    base::MessageLoop loop(message_loop_type);

    DelayTimerTarget target;
    base::DelayTimer timer(FROM_HERE, TimeDelta::FromMilliseconds(1), &target,
        &DelayTimerTarget::Signal);
    timer.Reset();

    bool did_run = false;
    OneShotTimerTester tester(&did_run, 100 /* milliseconds */);
    tester.Start();
    base::RunLoop().Run();

    ASSERT_TRUE(target.signaled());
}

struct ResetHelper {
    ResetHelper(base::DelayTimer* timer, DelayTimerTarget* target)
        : timer_(timer)
        , target_(target)
    {
    }

    void Reset()
    {
        ASSERT_FALSE(target_->signaled());
        timer_->Reset();
    }

private:
    base::DelayTimer* const timer_;
    DelayTimerTarget* const target_;
};

void RunTest_DelayTimer_Reset(base::MessageLoop::Type message_loop_type)
{
    base::MessageLoop loop(message_loop_type);

    // If Delay is never called, the timer shouldn't go off.
    DelayTimerTarget target;
    base::DelayTimer timer(FROM_HERE, TimeDelta::FromMilliseconds(50), &target,
        &DelayTimerTarget::Signal);
    timer.Reset();

    ResetHelper reset_helper(&timer, &target);

    base::OneShotTimer timers[20];
    for (size_t i = 0; i < arraysize(timers); ++i) {
        timers[i].Start(FROM_HERE, TimeDelta::FromMilliseconds(i * 10),
            &reset_helper, &ResetHelper::Reset);
    }

    bool did_run = false;
    OneShotTimerTester tester(&did_run, 300);
    tester.Start();
    base::RunLoop().Run();

    ASSERT_TRUE(target.signaled());
}

class DelayTimerFatalTarget {
public:
    void Signal()
    {
        ASSERT_TRUE(false);
    }
};

void RunTest_DelayTimer_Deleted(base::MessageLoop::Type message_loop_type)
{
    base::MessageLoop loop(message_loop_type);

    DelayTimerFatalTarget target;

    {
        base::DelayTimer timer(FROM_HERE, TimeDelta::FromMilliseconds(50), &target,
            &DelayTimerFatalTarget::Signal);
        timer.Reset();
    }

    // When the timer is deleted, the DelayTimerFatalTarget should never be
    // called.
    base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
}

} // namespace

//-----------------------------------------------------------------------------
// Each test is run against each type of MessageLoop.  That way we are sure
// that timers work properly in all configurations.

TEST(TimerTest, OneShotTimer)
{
    for (int i = 0; i < kNumTestingMessageLoops; i++) {
        RunTest_OneShotTimer(testing_message_loops[i]);
    }
}

TEST(TimerTest, OneShotTimer_Cancel)
{
    for (int i = 0; i < kNumTestingMessageLoops; i++) {
        RunTest_OneShotTimer_Cancel(testing_message_loops[i]);
    }
}

// If underline timer does not handle properly, we will crash or fail
// in full page heap environment.
TEST(TimerTest, OneShotSelfDeletingTimer)
{
    for (int i = 0; i < kNumTestingMessageLoops; i++) {
        RunTest_OneShotSelfDeletingTimer(testing_message_loops[i]);
    }
}

TEST(TimerTest, OneShotTimer_CustomTaskRunner)
{
    scoped_refptr<base::TestSimpleTaskRunner> task_runner = new base::TestSimpleTaskRunner();

    bool did_run = false;
    OneShotTimerTester f(&did_run);
    f.SetTaskRunner(task_runner);
    f.Start();

    EXPECT_FALSE(did_run);
    task_runner->RunUntilIdle();
    EXPECT_TRUE(did_run);
}

TEST(TimerTest, RepeatingTimer)
{
    for (int i = 0; i < kNumTestingMessageLoops; i++) {
        RunTest_RepeatingTimer(testing_message_loops[i],
            TimeDelta::FromMilliseconds(10));
    }
}

TEST(TimerTest, RepeatingTimer_Cancel)
{
    for (int i = 0; i < kNumTestingMessageLoops; i++) {
        RunTest_RepeatingTimer_Cancel(testing_message_loops[i],
            TimeDelta::FromMilliseconds(10));
    }
}

TEST(TimerTest, RepeatingTimerZeroDelay)
{
    for (int i = 0; i < kNumTestingMessageLoops; i++) {
        RunTest_RepeatingTimer(testing_message_loops[i],
            TimeDelta::FromMilliseconds(0));
    }
}

TEST(TimerTest, RepeatingTimerZeroDelay_Cancel)
{
    for (int i = 0; i < kNumTestingMessageLoops; i++) {
        RunTest_RepeatingTimer_Cancel(testing_message_loops[i],
            TimeDelta::FromMilliseconds(0));
    }
}

TEST(TimerTest, DelayTimer_NoCall)
{
    for (int i = 0; i < kNumTestingMessageLoops; i++) {
        RunTest_DelayTimer_NoCall(testing_message_loops[i]);
    }
}

TEST(TimerTest, DelayTimer_OneCall)
{
    for (int i = 0; i < kNumTestingMessageLoops; i++) {
        RunTest_DelayTimer_OneCall(testing_message_loops[i]);
    }
}

// It's flaky on the buildbot, http://crbug.com/25038.
TEST(TimerTest, DISABLED_DelayTimer_Reset)
{
    for (int i = 0; i < kNumTestingMessageLoops; i++) {
        RunTest_DelayTimer_Reset(testing_message_loops[i]);
    }
}

TEST(TimerTest, DelayTimer_Deleted)
{
    for (int i = 0; i < kNumTestingMessageLoops; i++) {
        RunTest_DelayTimer_Deleted(testing_message_loops[i]);
    }
}

TEST(TimerTest, MessageLoopShutdown)
{
    // This test is designed to verify that shutdown of the
    // message loop does not cause crashes if there were pending
    // timers not yet fired.  It may only trigger exceptions
    // if debug heap checking is enabled.
    bool did_run = false;
    {
        OneShotTimerTester a(&did_run);
        OneShotTimerTester b(&did_run);
        OneShotTimerTester c(&did_run);
        OneShotTimerTester d(&did_run);
        {
            base::MessageLoop loop;
            a.Start();
            b.Start();
        } // MessageLoop destructs by falling out of scope.
    } // OneShotTimers destruct.  SHOULD NOT CRASH, of course.

    EXPECT_FALSE(did_run);
}

void TimerTestCallback()
{
}

TEST(TimerTest, NonRepeatIsRunning)
{
    {
        base::MessageLoop loop;
        base::Timer timer(false, false);
        EXPECT_FALSE(timer.IsRunning());
        timer.Start(FROM_HERE, TimeDelta::FromDays(1),
            base::Bind(&TimerTestCallback));
        EXPECT_TRUE(timer.IsRunning());
        timer.Stop();
        EXPECT_FALSE(timer.IsRunning());
        EXPECT_TRUE(timer.user_task().is_null());
    }

    {
        base::Timer timer(true, false);
        base::MessageLoop loop;
        EXPECT_FALSE(timer.IsRunning());
        timer.Start(FROM_HERE, TimeDelta::FromDays(1),
            base::Bind(&TimerTestCallback));
        EXPECT_TRUE(timer.IsRunning());
        timer.Stop();
        EXPECT_FALSE(timer.IsRunning());
        ASSERT_FALSE(timer.user_task().is_null());
        timer.Reset();
        EXPECT_TRUE(timer.IsRunning());
    }
}

TEST(TimerTest, NonRepeatMessageLoopDeath)
{
    base::Timer timer(false, false);
    {
        base::MessageLoop loop;
        EXPECT_FALSE(timer.IsRunning());
        timer.Start(FROM_HERE, TimeDelta::FromDays(1),
            base::Bind(&TimerTestCallback));
        EXPECT_TRUE(timer.IsRunning());
    }
    EXPECT_FALSE(timer.IsRunning());
    EXPECT_TRUE(timer.user_task().is_null());
}

TEST(TimerTest, RetainRepeatIsRunning)
{
    base::MessageLoop loop;
    base::Timer timer(FROM_HERE, TimeDelta::FromDays(1),
        base::Bind(&TimerTestCallback), true);
    EXPECT_FALSE(timer.IsRunning());
    timer.Reset();
    EXPECT_TRUE(timer.IsRunning());
    timer.Stop();
    EXPECT_FALSE(timer.IsRunning());
    timer.Reset();
    EXPECT_TRUE(timer.IsRunning());
}

TEST(TimerTest, RetainNonRepeatIsRunning)
{
    base::MessageLoop loop;
    base::Timer timer(FROM_HERE, TimeDelta::FromDays(1),
        base::Bind(&TimerTestCallback), false);
    EXPECT_FALSE(timer.IsRunning());
    timer.Reset();
    EXPECT_TRUE(timer.IsRunning());
    timer.Stop();
    EXPECT_FALSE(timer.IsRunning());
    timer.Reset();
    EXPECT_TRUE(timer.IsRunning());
}

namespace {

bool g_callback_happened1 = false;
bool g_callback_happened2 = false;

void ClearAllCallbackHappened()
{
    g_callback_happened1 = false;
    g_callback_happened2 = false;
}

void SetCallbackHappened1()
{
    g_callback_happened1 = true;
    base::MessageLoop::current()->QuitWhenIdle();
}

void SetCallbackHappened2()
{
    g_callback_happened2 = true;
    base::MessageLoop::current()->QuitWhenIdle();
}

TEST(TimerTest, ContinuationStopStart)
{
    {
        ClearAllCallbackHappened();
        base::MessageLoop loop;
        base::Timer timer(false, false);
        timer.Start(FROM_HERE, TimeDelta::FromMilliseconds(10),
            base::Bind(&SetCallbackHappened1));
        timer.Stop();
        timer.Start(FROM_HERE, TimeDelta::FromMilliseconds(40),
            base::Bind(&SetCallbackHappened2));
        base::RunLoop().Run();
        EXPECT_FALSE(g_callback_happened1);
        EXPECT_TRUE(g_callback_happened2);
    }
}

TEST(TimerTest, ContinuationReset)
{
    {
        ClearAllCallbackHappened();
        base::MessageLoop loop;
        base::Timer timer(false, false);
        timer.Start(FROM_HERE, TimeDelta::FromMilliseconds(10),
            base::Bind(&SetCallbackHappened1));
        timer.Reset();
        // Since Reset happened before task ran, the user_task must not be cleared:
        ASSERT_FALSE(timer.user_task().is_null());
        base::RunLoop().Run();
        EXPECT_TRUE(g_callback_happened1);
    }
}

} // namespace
